package jmine.tec.report.metadata.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import jmine.tec.report.cell.DataCell;
import jmine.tec.report.chart.ChartType;
import jmine.tec.report.metadata.ColumnMetaData;
import jmine.tec.report.metadata.ModifiableColumnMetaData;
import jmine.tec.report.metadata.ParameterDefinition;
import jmine.tec.report.metadata.ParameterType;

/**
 * Simple implementation of {@link ColumnMetaData} that is {@link Serializable}.
 * 
 * @author takeshi
 */
public final class SimpleColumnMetaData implements ModifiableColumnMetaData, Serializable {

    private static final long serialVersionUID = 5799975948884957962L;

    private final String key;

    private final String label;

    private DataCell exampleDataCell;

    private EnumSet<ChartType> chartTypes = EnumSet.noneOf(ChartType.class);

    private final Map<String, ParameterDefinition> parameters = new HashMap<String, ParameterDefinition>();

    /**
     * Copy c'tor
     * 
     * @param meta {@link ColumnMetaData}
     * @param defs the {@link ParameterDefinition} a mais
     */
    public SimpleColumnMetaData(final ColumnMetaData meta, final ParameterDefinition... defs) {
        this.key = meta.getKey();
        this.label = meta.getLabel();
        this.parameters.putAll(meta.getAllParameterDefinitions());
        for (final ParameterDefinition parameterDefinition : defs) {
            this.parameters.put(parameterDefinition.getName(), parameterDefinition);
        }
    }

    /**
     * C'tor
     * 
     * @param key String
     * @param name String
     * @param defs the parameter definitions
     */
    public SimpleColumnMetaData(final String key, final String name, final ParameterDefinition... defs) {
        this.key = key;
        this.label = name;
        for (final ParameterDefinition parameterDefinition : defs) {
            this.parameters.put(parameterDefinition.getName(), parameterDefinition);
        }
    }

    /**
     * Adds an entity parameter
     * 
     * @param paramkey String
     * @param entitiesMap {@link Map} of {@link String} to {@link String}
     * @return {@link ColumnMetaData} this
     * @see br.com.maps.fof.report.descriptor.ColumnMetaData#addEntityParameter(java.lang.String, java.util.Map)
     */
    public ColumnMetaData addEntityParameter(final String paramkey, final Map<String, String> entitiesMap) {
        this.addParameter(paramkey, entitiesMap, ParameterType.ENTITY);
        return this;
    }

    /**
     * Adds a parameter
     * 
     * @param parameterDefinition the {@link ParameterDefinition}
     * @return {@link ColumnMetaData} this
     */
    public ColumnMetaData addParameter(final ParameterDefinition parameterDefinition) {
        this.parameters.put(parameterDefinition.getName(), parameterDefinition);
        return this;
    }

    /**
     * Adds a parameter to this column descriptor.
     * 
     * @param name the parameter name
     * @param value the default value
     * @param type the {@link ParameterType}
     * @return this (for cascading)
     * @see br.com.maps.fof.report.descriptor.ColumnMetaData#addParameter(java.lang.String, java.lang.Object,
     * br.com.maps.fof.report.descriptor.ParameterType)
     */
    public ColumnMetaData addParameter(final String name, final Object value, final ParameterType type) {
        return this.addParameter(name, name, value, type);
    }

    /**
     * Adds a parameter to this column descriptor.
     * 
     * @param name the parameter name
     * @param referenceValue the default value
     * @param paramLabel String the label
     * @param type the {@link ParameterType}
     * @return this (for cascading)
     * @see br.com.maps.fof.report.descriptor.ColumnMetaData#addParameter(java.lang.String, java.lang.Object,
     * br.com.maps.fof.report.descriptor.ParameterType)
     */
    public ColumnMetaData addParameter(final String name, final String paramLabel, final Object referenceValue,
            final ParameterType type) {
        this.parameters.put(name, new ParameterDefinition(name, paramLabel, referenceValue, type));
        return this;
    }

    /**
     * @param paramkey String
     * @param paramlabel String
     * @param type type
     * @param reference ref
     * @return this
     * @see br.com.maps.util.report.metadata.ModifiableColumnMetaData#addParameter(java.lang.String, java.lang.String,
     * br.com.maps.util.report.metadata.ParameterType, java.lang.Object)
     */
    public ModifiableColumnMetaData addParameter(final String paramkey, final String paramlabel,
            final ParameterType type, final Object reference) {
        this.addParameter(paramkey, paramlabel, reference, type);
        return this;
    }

    /**
     * @param param {@link ParameterDefinition}
     * @return this
     */
    public ModifiableColumnMetaData addParameterDefinition(final ParameterDefinition param) {
        this.parameters.put(param.getName(), param);
        return this;
    }

    /**
     * @return {@link Map}
     * @see br.com.maps.util.report.metadata.ColumnMetaData#getAllParameterDefinitions()
     */
    public Map<String, ParameterDefinition> getAllParameterDefinitions() {
        return new HashMap<String, ParameterDefinition>(this.parameters);
    }

    /**
     * @return {@link Map}
     * @see br.com.maps.fof.report.descriptor.ColumnMetaData#getAllParameters()
     */
    public Map<String, ParameterDefinition> getAllParameters() {
        return new HashMap<String, ParameterDefinition>(this.parameters);
    }

    /**
     * @param name name of the parameter
     * @return Object
     */
    public Object getDefaultParameterValue(final String name) {
        final ParameterDefinition def = this.parameters.get(name);
        return def == null ? null : def.getReference();
    }

    /**
     * @param name String
     * @param defaultValue Object
     * @return Object
     */
    public Object getDefaultParameterValue(final String name, final Object defaultValue) {
        final Object val = this.getDefaultParameterValue(name);
        if (val == null) {
            return defaultValue;
        }
        return val;
    }

    /**
     * @return String
     */
    public String getKey() {
        return this.key;
    }

    /**
     * @return String
     */
    public String getLabel() {
        return this.label;
    }

    /**
     * @return {@link Map}
     */
    public Map<String, String> getParameterDefinition() {
        final Map<String, String> labels = new HashMap<String, String>();
        for (final ParameterDefinition def : this.parameters.values()) {
            labels.put(def.getName(), def.getLabel());
        }
        return labels;
    }

    /**
     * @param localKey {@link String}
     * @return {@link ParameterDefinition}
     * @see br.com.maps.util.report.metadata.ColumnMetaData#getParameterDefinition(java.lang.String)
     */
    public ParameterDefinition getParameterDefinition(final String localKey) {
        return this.parameters.get(localKey);
    }

    /**
     * @return {@link Collection}
     * @see br.com.maps.util.report.metadata.ColumnMetaData#getParameters()
     */
    public Collection<ParameterDefinition> getParameters() {
        return new ArrayList<ParameterDefinition>(this.parameters.values());
    }

    /**
     * Gets the parameter type given its name.
     * 
     * @param parameterName the parameter name.
     * @return {@link ParameterType}
     */
    public ParameterType getParameterType(final String parameterName) {
        final ParameterDefinition def = this.parameters.get(parameterName);
        return def == null ? ParameterType.OBJECT : def.getType();
    }

    /**
     * Gets the paramenters in a Map
     * 
     * @return the {@link Map} containing the parameters
     */
    public Map<String, Object> getParamMap() {
        final Map<String, Object> paramMap = new HashMap<String, Object>();
        for (final ParameterDefinition def : this.parameters.values()) {
            paramMap.put(def.getName(), def.getReference());
        }
        return paramMap;
    }

    /**
     * @param name String
     * @return boolean
     */
    public boolean hasParameter(final String name) {
        return this.parameters.containsKey(name);
    }

    /**
     * @return String
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Name: " + this.label + " (" + this.key + ") with params: " + this.parameters.keySet();
    }

    /**
     * @return the exampleDataCell
     */
    public DataCell getExampleDataCell() {
        return this.exampleDataCell;
    }

    /**
     * @param exampleDataCell the exampleDataCell to set
     */
    public void setExampleDataCell(final DataCell exampleDataCell) {
        this.exampleDataCell = exampleDataCell;
    }

    /**
     * @return the chartTypes
     */
    public EnumSet<ChartType> getChartTypes() {
        return this.chartTypes;
    }

    /**
     * @param chartTypes the chartTypes to set
     */
    public void setChartTypes(final EnumSet<ChartType> chartTypes) {
        this.chartTypes = chartTypes;
    }

    /**
     * @return {@link UnmodifiableColumnMetaData}
     */
    public UnmodifiableColumnMetaData toUnmodifiable() {
        return new UnmodifiableColumnMetaData(this);
    }
}
